/***************************2.1: ACK/NACK*****************/
/***** Feng Hong; 2015-12-09******************************/
package com.ouc.tcp.test;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Vector;

import com.ouc.tcp.client.Client;
import com.ouc.tcp.client.TCP_Receiver_ADT;
import com.ouc.tcp.message.*;
import com.ouc.tcp.tool.TCP_TOOL;

public class TCP_Receiver extends TCP_Receiver_ADT {
	
	private TCP_PACKET ackPack;	//回复的ACK报文段
	int sequence=1;//用于记录当前待接收的包序号，注意包序号不完全是
	int seq=-1;//记录当前已收到的、正确的最后一个数据报的序列号
	
	//实例化一个SR接收端窗口
	private ReceiveWindow_SR SR_window = new ReceiveWindow_SR(client);
	
	/*构造函数*/
	public TCP_Receiver() {
		super();	//调用超类构造函数
		super.initTCP_Receiver(this);	//初始化TCP接收端
	}

	@Override
	//接收到数据报：检查校验和，设置回复的ACK报文段
	public void rdt_recv(TCP_PACKET recvPack) {
		
//		//检查校验码，生成ACK
//		if(CheckSum.computeChkSum(recvPack) == recvPack.getTcpH().getTh_sum()) {
//			//生成ACK报文段（设置确认号）
//			tcpH.setTh_ack(recvPack.getTcpH().getTh_seq());
//			ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
//			tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
//			//回复ACK报文段
//			reply(ackPack);	
//			
//			//RDT2.2 如果新的数据报被接收并核对通过，更新seq
//			if (recvPack.getTcpH().getTh_seq() != seq) {
//                seq = recvPack.getTcpH().getTh_seq(); // update seq
//            } else {
//                System.out.println("[!] seq repeated!");
//            }
//			
//			//将接收到的正确有序的数据插入data队列，准备交付
//			dataQueue.add(recvPack.getTcpS().getData());
//			sequence++;
//		}
//		else{
//			System.out.println("Recieve Computed: "+CheckSum.computeChkSum(recvPack));
//			System.out.println("Recieved Packet"+recvPack.getTcpH().getTh_sum());
//			System.out.println("Problem: Packet Number: "+recvPack.getTcpH().getTh_seq()+" + InnerSeq:  "+sequence);
//			tcpH.setTh_ack(seq);//RDT2.2 数据报核对后不符合，则回复上一个数据报的序号作为ACK
//			ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
//			tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
//			//回复ACK报文段
//			reply(ackPack);
//		}
//		
//		System.out.println();
//		
//		
//		//交付数据（每20组数据交付一次）
//		if(dataQueue.size() == 20) 
//			deliver_data();	
		
		/*
		 * SR选择响应协议版本
		 */
		int seq_recvPack = recvPack.getTcpH().getTh_seq();
		if(CheckSum.computeChkSum(recvPack) == recvPack.getTcpH().getTh_sum() && seq_recvPack >= SR_window.base && seq_recvPack < SR_window.base + SR_window.size) {
			//是我期望的序号 && 校验通过
			//生成ACK报文段（设置确认号）
			tcpH.setTh_ack(recvPack.getTcpH().getTh_seq());
			ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
			tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
			//回复ACK报文段
			try {
				Vector<TCP_PACKET> vector = SR_window.recvPacket(recvPack.clone());
				if(vector != null && vector.size() > 0) {
					for (int i = 0; i < vector.size(); i++) {
						dataQueue.add(vector.get(i).getTcpS().getData());
					}
					//SR版本修改交付情况,我们所使用的vector是有序的，所以只需要核对第一个期待值，满足条件即可向系统递交分组。
					deliver_data();
				}
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
			reply(ackPack);
		}
		else if(seq_recvPack < SR_window.base && seq_recvPack > SR_window.base - SR_window.size) {
			//收到了一个序号小于我的包
			//SR版本：收到了一个窗口以外的包
//			System.out.println("该包在窗口以外");
			tcpH.setTh_ack(seq_recvPack);
			ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
			tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
			//回复ACK报文段
			reply(ackPack);
		}
		else {
			//SR版本：其他情况不用回复ACK，丢弃分组
		}
	}

	@Override
	//交付数据（将数据写入文件）；不需要修改
	public void deliver_data() {
		//检查dataQueue，将数据写入文件
		File fw = new File("recvData.txt");
		BufferedWriter writer;
		
		try {
			writer = new BufferedWriter(new FileWriter(fw, true));
			
			//循环检查data队列中是否有新交付数据
			while(!dataQueue.isEmpty()) {
				int[] data = dataQueue.poll();
				
				//将数据写入文件
				for(int i = 0; i < data.length; i++) {
					writer.write(data[i] + "\n");
				}
				
				writer.flush();		//清空输出缓存
			}
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	//回复ACK报文段
	public void reply(TCP_PACKET replyPack) {
		//设置错误控制标志
		tcpH.setTh_eflag((byte)7);	//eFlag=0，信道无错误
				
		//发送数据报
		client.send(replyPack);
	}
	
}
